#ifndef     __GPIO_H
#define     __GPIO_H

#include "./compat.h"

namespace device {

enum class gpio_mode_t : uint8_t {
    analog = 0x00,
    float_input = 0x04,
    input = 0x08,
    reserved = 0x0C,

    push_pull_output = 0x03,
    open_drain_output = 0x07,
    push_pull_af = 0x0B,
    open_drain_af = 0x0F,
};

enum class gpio_pupd_t : uint8_t {
    no_pull = 0x00,
    up = 0x01,
    down = 0x02,
};

template <uint32_t Base, uint32_t Pin>
struct gpio_t {
    
    template <gpio_mode_t mode>
    static inline void set_mode() {
        static_assert(Pin <= 15, "pin must <= 15");

        if constexpr (Pin < 8) {
            constexpr uint32_t pin = Pin;
            constexpr uint32_t clr = ~(0x0F << (pin * 4));
            constexpr uint32_t tmp = static_cast<uint8_t>(mode) << (pin * 4);
            GPIO_CTL0(Base) = GPIO_CTL0(Base) & clr | tmp;
        } else {
            constexpr uint32_t pin = Pin - 8;
            constexpr uint32_t clr = ~(0x0F << (pin * 4));
            constexpr uint32_t tmp = static_cast<uint8_t>(mode) << (pin * 4);
            GPIO_CTL1(Base) = GPIO_CTL1(Base) & clr | tmp;
        }
    }

    template <gpio_pupd_t pupd>
    static inline void set_pupd() {
        if constexpr (pupd == gpio_pupd_t::up) {
            set();
        } else if constexpr (pupd == gpio_pupd_t::down) {
            clr();
        }
    }

    static inline bool read() {
        constexpr uint32_t mask = 0x01 << Pin;
        return 0x00 != (GPIO_ISTAT(Base) & mask);
    }

    static inline void set() {
        constexpr uint32_t mask = 0x01 << Pin;
        GPIO_BOP(Base) = mask;
    }

    static inline void clr() {
        constexpr uint32_t mask = 0x01 << Pin;
        GPIO_BC(Base) = mask;
    }
};

template <typename... Ts>
inline void config_pp_output() {
    ((Ts::template set_mode<gpio_mode_t::push_pull_output>(),
    Ts::clr()), ...);
}

template <typename... Ts>
inline void config_od_output() {
    ((Ts::template set_mode<gpio_mode_t::open_drain_output>(),
    Ts::set()), ...);
}

template <typename... Ts>
inline void config_analog() {
    (Ts:: template set_mode<gpio_mode_t::analog>(), ...);
}

template <typename... Ts>
inline void config_up_input() {
    ((Ts::template set_mode<gpio_mode_t::input>(),
      Ts::template set_pupd<gpio_pupd_t::up>()),...);
}

template <typename... Ts>
inline void config_down_input() {
    ((Ts::template set_mode<gpio_mode_t::input>(),
      Ts::template set_pupd<gpio_pupd_t::down>()),...);
}

template <typename... Ts>
inline void config_float_input() {
    (Ts::template set_mode<gpio_mode_t::float_input>(),...);
}

template <typename... Ts>
inline void config_pp_af() {
    ((Ts::template set_mode<gpio_mode_t::push_pull_af>()),...);
}

template <uint32_t Pin>
using PA = gpio_t<GPIOA, Pin>;

template <uint32_t Pin>
using PB = gpio_t<GPIOB, Pin>;

template <uint32_t Pin>
using PC = gpio_t<GPIOC, Pin>;

template <uint32_t Pin>
using PD = gpio_t<GPIOD, Pin>;

template <uint32_t Pin>
using PE = gpio_t<GPIOE, Pin>;

template <uint32_t Pin>
using PF = gpio_t<GPIOF, Pin>;

template <uint32_t Pin>
using PG = gpio_t<GPIOG, Pin>;

}



#endif
